package top.v5it.japi.plus.extra.web;

import cn.hutool.core.lang.Assert;
import cn.hutool.extra.spring.SpringUtil;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import top.v5it.japi.plus.extra.exception.JapiMethodDenyException;
import top.v5it.japi.plus.extra.exception.JapiMethodNotFoundException;
import top.v5it.japi.plus.extra.util.BeanUtils;

import javax.servlet.http.HttpServletRequest;
import javax.validation.ConstraintViolation;
import javax.validation.Validation;
import javax.validation.Validator;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.*;

/**
 * base controller
 *
 * @author zhanpu
 * @date 2022/8/11
 */
public class JapiBaseController {

    /**
     * request parameter to map
     *
     * @param json
     * @param request
     * @return
     */
    protected Map<String, Object> toMap(Map<String, Object> json, HttpServletRequest request) {
        return Optional
                .ofNullable(json)
                .orElseGet(() -> {
                    final Map<String, Object> params = new HashMap<>();
                    for (Enumeration<String> e = request.getParameterNames(); e.hasMoreElements(); ) {
                        String key = e.nextElement();
                        String value = request.getParameter(key);
                        params.put(key, value);
                    }
                    return params;
                });
    }

    /**
     * get bean in the spring context by name.
     *
     * @param name the bean name
     * @return the instance of the bean
     */
    protected <T> T getBean(String name) {
        Assert.notEmpty(name, () -> new IllegalArgumentException("请求参数[bean]不能为空"));
        final String beanName = name.concat("Service");
        try {
            return SpringUtil.getBean(beanName);
        } catch (NoSuchBeanDefinitionException e) {
            throw new RuntimeException("No such bean definition in the spring context!", e);
        } catch (BeansException e) {
            throw new RuntimeException("error when get the bean[" + beanName + "]");
        }
    }

    /**
     * 使用反射，通过名称获取服务的方法
     *
     * @param service 服务
     * @param bean    服务名前缀
     * @param name    方法名
     * @return {@link Method}
     */
    protected Method getMethod(Object service, String bean, String name) {
        Assert.notEmpty(name, () -> new IllegalArgumentException("请求参数[method]不能为空"));
        // 拒绝非公开访问方法
        JapiDenyCnf japiDenyCnf = SpringUtil.getBean(JapiDenyCnf.class);
        final boolean exists = japiDenyCnf.exists(bean, name, true);
        Assert.isFalse(exists, () -> new JapiMethodDenyException("请求method[" + name + "]禁止访问"));
        Method[] allOfMethods = service.getClass().getDeclaredMethods();
        Method rtn = null;
        for (Method method : allOfMethods) {
            if (method.getName().equals(name)) rtn = method;
        }
        if (rtn != null) {
            // if have override, return the last one.
            return rtn;
        }
        throw new JapiMethodNotFoundException("No such method[" + name + "] in the service[" + service.getClass().getName() + "]");
    }

    /**
     * 验证vo对象字段合法性
     *
     * @param vo
     */
    protected void validate(Object vo) {
        Validator validator = Validation.buildDefaultValidatorFactory().getValidator();
        Set<ConstraintViolation<Object>> set = validator.validate(vo);
        for (ConstraintViolation<Object> constraintViolation : set) {
            throw new IllegalArgumentException(constraintViolation.getMessage());
        }
    }

    /**
     * get the arguments that invoke the method need.
     * The first thing is put the value object to the first argument, if it's available.
     * Then put the other arguments in order.
     *
     * @param method
     * @param json
     * @param vo
     * @return the arguments
     */
    protected Object[] getArguments(Method method, Map<String, Object> json, Object vo) {
        int length = method.getParameterCount();
        if (length == 0) return null;
        int index = 0;
        List<Object> args = new ArrayList<>();
        //add the value object to the first argument, if it's available.
        if (vo != null) {
            args.add(vo);
            index++;
        }
        //add the other arguments in order.
        Parameter[] allOfParameters = method.getParameters();
        for (; index < length; index++) {
            Parameter parameter = allOfParameters[index];
            String name = parameter.getName();
            Object value = BeanUtils.bind(parameter.getParameterizedType(), json.get(name));
            args.add(value);
        }
        return args.toArray();
    }
}
